import { PatrolDetectionPoint } from "../api/gis";
import { LatLngRect, pointIntersectsRect, getCorrectLngRange } from "../utils/earthUtils";

// 经纬度范围类型
interface LatLngBounds {
  minLat: number;
  maxLat: number;
  minLng: number;
  maxLng: number;
}

// 经纬度点类型
interface LatLngPoint {
  lat: number;
  lng: number;
}

// 配置选项类型
interface EarthPieceRendererOptions {
  tileUrlTemplate: string; // 瓦片URL模板
  maxLevel?: number; // 最大瓦片层级
  latLngBounds?: LatLngBounds; // 巡视区的经纬度范围
  simulatedMetaKey?: boolean; // 模拟Meta键
  onCoordinateClick?: (coord: { lat: number; lng: number } | null) => void;
  onCoordinateHover?: (coord: { lat: number; lng: number } | null) => void;
  onDetectionPointClick?: (detectionPoint: PatrolDetectionPoint | null) => void;
  onDetectionPointHover?: (detectionPoint: PatrolDetectionPoint | null) => void;
  onSelectionEnd?: (selectionRect: LatLngRect, intersectingDetectionPoints: PatrolDetectionPoint[]) => void;
}

export class EarthPieceRenderer {
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  private tileUrlTemplate: string;
  private maxLevel: number;
  private latLngBounds: LatLngBounds;
  private simulatedMetaKey: boolean;
  private tileCache: Map<string, HTMLImageElement> = new Map();
  private tileLoadingSet: Set<string> = new Set();
  private failedTileSet: Set<string> = new Set();

  // 变换状态
  private logicalScale = 1; // 逻辑缩放，相对于基准大小(1024)
  private panX = 0;
  private panY = 0;
  private isDragging = false;
  private dragOffset: { x: number; y: number } | null = null;
  private currentLevel = 1;

  // 获取实际渲染时的缩放比例
  private get scale(): number {
    // 渲染缩放 = 逻辑缩放 / 地图大小倍数
    // 这样切换level时，虽然地图大小翻倍，但渲染缩放减半，保持视觉连续性
    const levelMultiplier = Math.pow(2, this.currentLevel - 1);
    return this.logicalScale / levelMultiplier;
  }

  // 数据
  private detectionPoints: PatrolDetectionPoint[] = [];

  // 悬停状态
  private hoveredDetectionPoint: PatrolDetectionPoint | null = null;

  // 缓存名称显示状态，避免频繁计算
  private nameVisibilityCache: {
    scale: number;
    pointCount: number;
    shouldShowNames: boolean[];
  } | null = null;

  // 选区状态（参考其他组件的实现）
  private selectionClickCount = 0;
  private selectionPoints: [LatLngPoint, LatLngPoint] | null = null;
  private selectedPoints: Set<PatrolDetectionPoint> = new Set();

  // 回调函数
  private onCoordinateClick?: (coord: { lat: number; lng: number } | null) => void;
  private onCoordinateHover?: (coord: { lat: number; lng: number } | null) => void;
  private onDetectionPointClick?: (detectionPoint: PatrolDetectionPoint | null) => void;
  private onDetectionPointHover?: (detectionPoint: PatrolDetectionPoint | null) => void;
  private onSelectionEnd?: (selectionRect: LatLngRect, intersectingDetectionPoints: PatrolDetectionPoint[]) => void;

  // 动画帧ID
  private animationFrameId: number | null = null;

  // 事件监听器引用（用于清理）
  private boundHandleWheel: (e: WheelEvent) => void;
  private boundHandleMouseDown: (e: MouseEvent) => void;
  private boundHandleMouseMove: (e: MouseEvent) => void;
  private boundHandleMouseUp: (e: MouseEvent) => void;
  private boundHandleClick: (e: MouseEvent) => void;
  private boundHandleResize: () => void;

  constructor(canvas: HTMLCanvasElement, options: EarthPieceRendererOptions) {
    this.canvas = canvas;
    const ctx = canvas.getContext("2d");
    if (!ctx) {
      throw new Error("无法获取2D渲染上下文");
    }
    this.ctx = ctx;

    // 设置配置选项
    this.tileUrlTemplate = options.tileUrlTemplate;
    this.maxLevel = options.maxLevel || 5;
    this.simulatedMetaKey = options.simulatedMetaKey || false;

    // 设置经纬度范围
    this.latLngBounds = options.latLngBounds || {
      minLat: -90,
      maxLat: 90,
      minLng: -180,
      maxLng: 180,
    };

    // 设置回调函数
    this.onCoordinateClick = options.onCoordinateClick;
    this.onCoordinateHover = options.onCoordinateHover;
    this.onDetectionPointClick = options.onDetectionPointClick;
    this.onDetectionPointHover = options.onDetectionPointHover;
    this.onSelectionEnd = options.onSelectionEnd;

    // 绑定事件处理器
    this.boundHandleWheel = this.handleWheel.bind(this);
    this.boundHandleMouseDown = this.handleMouseDown.bind(this);
    this.boundHandleMouseMove = this.handleMouseMove.bind(this);
    this.boundHandleMouseUp = this.handleMouseUp.bind(this);
    this.boundHandleClick = this.handleClick.bind(this);
    this.boundHandleResize = this.handleResize.bind(this);

    // 初始化
    this.initCanvas();
    this.attachEventListeners();
    this.startRenderLoop();
  }

  // 初始化画布
  private initCanvas(): void {
    this.handleResize();
    this.fitToCanvas();
  }

  // 居中视图
  private centerView(): void {
    // 使用基准大小1024
    const baseSize = 1024;

    // 计算缩放后的大小（基于逻辑缩放）
    const scaledSize = baseSize * this.logicalScale;

    // 将地图居中显示
    this.panX = (this.canvas.width - scaledSize) / 2;
    this.panY = (this.canvas.height - scaledSize) / 2;
  }

  // 自适应缩放，让整个区域适配canvas
  private fitToCanvas(): void {
    // 使用基准大小1024
    const baseSize = 1024;

    // 计算需要的逻辑缩放比例，让地图完整显示在canvas中，留出10%的边距
    const scaleX = (this.canvas.width * 0.9) / baseSize;
    const scaleY = (this.canvas.height * 0.9) / baseSize;

    // 使用较小的缩放比例，确保整个地图都能显示
    this.logicalScale = Math.min(scaleX, scaleY, 10); // 最大不超过10倍
    this.logicalScale = Math.max(this.logicalScale, 0.5); // 最小不小于0.5倍

    // 更新层级
    this.currentLevel = this.calculateLevel(this.logicalScale);

    // 重新居中
    this.centerView();
  }

  // 处理窗口大小变化
  private handleResize(): void {
    if (!this.canvas.parentElement) return;
    const rect = this.canvas.parentElement.getBoundingClientRect();
    this.canvas.width = rect.width;
    this.canvas.height = rect.height;
  }

  // 附加事件监听器
  private attachEventListeners(): void {
    this.canvas.addEventListener("wheel", this.boundHandleWheel);
    this.canvas.addEventListener("mousedown", this.boundHandleMouseDown);
    this.canvas.addEventListener("mousemove", this.boundHandleMouseMove);
    this.canvas.addEventListener("mouseup", this.boundHandleMouseUp);
    this.canvas.addEventListener("mouseleave", this.boundHandleMouseUp);
    this.canvas.addEventListener("click", this.boundHandleClick);
    window.addEventListener("resize", this.boundHandleResize);
  }

  // 移除事件监听器
  private removeEventListeners(): void {
    this.canvas.removeEventListener("wheel", this.boundHandleWheel);
    this.canvas.removeEventListener("mousedown", this.boundHandleMouseDown);
    this.canvas.removeEventListener("mousemove", this.boundHandleMouseMove);
    this.canvas.removeEventListener("mouseup", this.boundHandleMouseUp);
    this.canvas.removeEventListener("mouseleave", this.boundHandleMouseUp);
    this.canvas.removeEventListener("click", this.boundHandleClick);
    window.removeEventListener("resize", this.boundHandleResize);
  }

  // 计算当前应该显示的瓦片级别（基于逻辑缩放）
  private calculateLevel(logicalScale: number): number {
    const calculatedLevel = Math.floor(Math.log2(logicalScale)) + 1;
    return Math.min(Math.max(calculatedLevel, 1), this.maxLevel);
  }

  // 加载瓦片图
  private async loadTile(level: number, row: number, col: number): Promise<HTMLImageElement | null> {
    const key = `${level}_${row}_${col}`;

    if (this.tileCache.has(key)) {
      return this.tileCache.get(key)!;
    }

    if (this.tileLoadingSet.has(key) || this.failedTileSet.has(key)) {
      return null;
    }

    this.tileLoadingSet.add(key);

    try {
      const url = this.tileUrlTemplate
        .replace("{z}", level.toString())
        .replace("{y}", row.toString())
        .replace("{x}", col.toString());

      const img = new Image();

      return new Promise((resolve) => {
        img.onload = () => {
          this.tileCache.set(key, img);
          this.tileLoadingSet.delete(key);
          resolve(img);
        };

        img.onerror = () => {
          this.tileLoadingSet.delete(key);
          this.failedTileSet.add(key);
          console.error(`加载瓦片失败: ${key}`);
          resolve(null);
        };

        img.src = url;
      });
    } catch (error) {
      console.error(`加载瓦片失败: ${key}`, error);
      this.tileLoadingSet.delete(key);
      this.failedTileSet.add(key);
      return null;
    }
  }

  // 获取可见的瓦片
  private getVisibleTiles(level: number): Array<{ row: number; col: number }> {
    const tiles: Array<{ row: number; col: number }> = [];

    if (level === 1) {
      tiles.push({ row: 1, col: 1 });
    } else {
      const tileCount = Math.pow(2, level - 1);
      const tileSize = 1024;

      const viewportLeft = -this.panX / this.scale;
      const viewportRight = (this.canvas.width - this.panX) / this.scale;
      const viewportTop = -this.panY / this.scale;
      const viewportBottom = (this.canvas.height - this.panY) / this.scale;

      const startCol = Math.max(1, Math.floor(viewportLeft / tileSize) + 1);
      const endCol = Math.min(tileCount, Math.ceil(viewportRight / tileSize));
      const startRow = Math.max(1, Math.floor(viewportTop / tileSize) + 1);
      const endRow = Math.min(tileCount, Math.ceil(viewportBottom / tileSize));

      for (let row = startRow; row <= endRow; row++) {
        for (let col = startCol; col <= endCol; col++) {
          tiles.push({ row, col });
        }
      }
    }

    return tiles;
  }

  // 获取当前层级的地图大小
  private getMapSize(level: number = this.currentLevel): number {
    return level === 1 ? 1024 : 1024 * Math.pow(2, level - 1);
  }

  // 加载可见瓦片
  private loadVisibleTiles(): void {
    // 使用当前层级加载瓦片
    const visibleTiles = this.getVisibleTiles(this.currentLevel);
    visibleTiles.forEach(({ row, col }) => {
      this.loadTile(this.currentLevel, row, col);
    });
  }

  // 绘制五角星的辅助方法
  private drawStar(ctx: CanvasRenderingContext2D, cx: number, cy: number, radius: number): void {
    const spikes = 5;
    const outerRadius = radius;
    const innerRadius = radius * 0.4;

    ctx.beginPath();
    for (let i = 0; i < spikes * 2; i++) {
      const angle = (Math.PI / spikes) * i - Math.PI / 2;
      const r = i % 2 === 0 ? outerRadius : innerRadius;
      const x = cx + Math.cos(angle) * r;
      const y = cy + Math.sin(angle) * r;

      if (i === 0) {
        ctx.moveTo(x, y);
      } else {
        ctx.lineTo(x, y);
      }
    }
    ctx.closePath();
    ctx.fill();
    ctx.stroke();
  }

  // 计算探测点密度和决定是否显示名称（带缓存优化）
  private shouldShowDetectionName(pointPositions: Array<{ point: PatrolDetectionPoint; screenX: number; screenY: number }>): boolean[] {
    if (pointPositions.length === 0) return [];
    
    // 检查缓存是否有效
    if (this.nameVisibilityCache && 
        Math.abs(this.nameVisibilityCache.scale - this.scale) < 0.01 && 
        this.nameVisibilityCache.pointCount === pointPositions.length) {
      return this.nameVisibilityCache.shouldShowNames;
    }
    
    // 计算当前缩放级别下的最小显示间距（像素）
    const minDisplayDistance = 60 / this.scale; // 根据缩放调整最小间距
    
    // 计算探测点之间的平均距离
    let totalDistance = 0;
    let distanceCount = 0;
    
    for (let i = 0; i < pointPositions.length; i++) {
      for (let j = i + 1; j < pointPositions.length; j++) {
        const distance = Math.sqrt(
          Math.pow(pointPositions[i].screenX - pointPositions[j].screenX, 2) +
          Math.pow(pointPositions[i].screenY - pointPositions[j].screenY, 2)
        );
        totalDistance += distance;
        distanceCount++;
      }
    }
    
    const averageDistance = distanceCount > 0 ? totalDistance / distanceCount : 0;
    
    // 如果平均距离小于最小显示间距，则需要隐藏部分名称
    const shouldHideNames = averageDistance < minDisplayDistance;
    
    let showNames: boolean[];
    
    if (!shouldHideNames) {
      // 距离足够大，显示所有名称
      showNames = new Array(pointPositions.length).fill(true);
    } else {
      // 距离太近，需要智能隐藏部分名称
      showNames = new Array(pointPositions.length).fill(false);
      
      // 优先显示特殊探测点的名称
      const specialPoints = pointPositions.map((pos, index) => ({ ...pos, index }))
        .filter(pos => pos.point.is_special !== 0);
      
      // 优先显示特殊点
      specialPoints.forEach(({ index }) => {
        showNames[index] = true;
      });
      
      // 对于普通点，按重要性排序显示
      const normalPoints = pointPositions.map((pos, index) => ({ ...pos, index }))
        .filter(pos => pos.point.is_special === 0);
      
      // 按ID排序，优先显示ID较小的点（通常更重要）
      normalPoints.sort((a, b) => a.point.id - b.point.id);
      
      // 计算最多显示多少个普通点的名称（避免过于密集）
      const maxNormalNames = Math.max(1, Math.floor(pointPositions.length * 0.3)); // 最多显示30%的普通点名称
      
      for (let i = 0; i < Math.min(maxNormalNames, normalPoints.length); i++) {
        const { index } = normalPoints[i];
        showNames[index] = true;
      }
    }
    
    // 更新缓存
    this.nameVisibilityCache = {
      scale: this.scale,
      pointCount: pointPositions.length,
      shouldShowNames: showNames
    };
    
    return showNames;
  }

  // 渲染函数
  private render(): void {
    const ctx = this.ctx;
    const width = this.canvas.width;
    const height = this.canvas.height;

    // 清空画布
    ctx.clearRect(0, 0, width, height);
    ctx.fillStyle = "#000000";
    ctx.fillRect(0, 0, width, height);

    // 应用变换
    ctx.save();
    ctx.translate(this.panX, this.panY);
    ctx.scale(this.scale, this.scale);

    // 绘制瓦片底图
    const level = this.currentLevel;
    const tileSize = 1024;
    const currentMapSize = this.getMapSize(level);

    // 1. 始终先绘制level 1作为底图（如果存在）
    const level1Img = this.tileCache.get(`1_1_1`);
    if (level1Img) {
      // level 1的图需要拉伸以覆盖整个当前地图区域
      ctx.drawImage(level1Img, 0, 0, 1024, 1024, 0, 0, currentMapSize, currentMapSize);
    } else {
      // 如果level 1也没有，显示占位符
      ctx.fillStyle = "#333333";
      ctx.fillRect(0, 0, currentMapSize, currentMapSize);
    }

    // 2. 如果当前层级大于1，绘制当前层级的瓦片（覆盖在level 1上）
    if (level > 1) {
      const visibleTiles = this.getVisibleTiles(level);

      visibleTiles.forEach(({ row, col }) => {
        const key = `${level}_${row}_${col}`;
        const img = this.tileCache.get(key);

        // 只绘制已加载的瓦片，未加载的瓦片保留底图
        if (img) {
          const x = (col - 1) * tileSize;
          const y = (row - 1) * tileSize;
          ctx.drawImage(img, x, y, tileSize, tileSize);
        }
      });
    }

    // 计算所有探测点的屏幕坐标
    const mapSize = this.getMapSize(level);
    const latRange = this.latLngBounds.maxLat - this.latLngBounds.minLat;
    const lngRange = this.latLngBounds.maxLng - this.latLngBounds.minLng;

    const pointPositions = this.detectionPoints.map((point) => {
      const normalizedLng = (point.center_longitude - this.latLngBounds.minLng) / lngRange;
      const normalizedLat = (point.center_latitude - this.latLngBounds.minLat) / latRange;
      const screenX = normalizedLng * mapSize;
      const screenY = (1 - normalizedLat) * mapSize;
      return { point, screenX, screenY };
    });

    // 绘制探测点之间的连线
    let sortedPositions: Array<{ point: PatrolDetectionPoint; screenX: number; screenY: number; order: number }> = [];
    if (pointPositions.length > 1) {
      // 按照探测点ID排序（假设ID是按顺序的）
      sortedPositions = [...pointPositions]
        .sort((a, b) => a.point.id - b.point.id)
        .map((pos, index) => ({ ...pos, order: index + 1 }));

      ctx.strokeStyle = "rgba(0, 255, 255, 0.6)"; // 青色半透明连线
      ctx.lineWidth = 2 / this.scale;
      ctx.setLineDash([5 / this.scale, 5 / this.scale]); // 虚线

      // 绘制连线和箭头
      for (let i = 0; i < sortedPositions.length - 1; i++) {
        const current = sortedPositions[i];
        const next = sortedPositions[i + 1];

        // 绘制线段
        ctx.beginPath();
        ctx.moveTo(current.screenX, current.screenY);
        ctx.lineTo(next.screenX, next.screenY);
        ctx.stroke();

        // 在线段中点绘制箭头
        const midX = (current.screenX + next.screenX) / 2;
        const midY = (current.screenY + next.screenY) / 2;
        const angle = Math.atan2(next.screenY - current.screenY, next.screenX - current.screenX);

        // 绘制箭头
        ctx.save();
        ctx.setLineDash([]); // 箭头用实线
        ctx.fillStyle = "rgba(0, 255, 255, 0.8)";
        ctx.translate(midX, midY);
        ctx.rotate(angle);

        const arrowSize = 8 / this.scale;
        ctx.beginPath();
        ctx.moveTo(0, 0);
        ctx.lineTo(-arrowSize, -arrowSize / 2);
        ctx.lineTo(-arrowSize, arrowSize / 2);
        ctx.closePath();
        ctx.fill();
        ctx.restore();
      }

      ctx.setLineDash([]); // 重置为实线
    }

    // 创建序号映射
    const orderMap = new Map<PatrolDetectionPoint, number>();
    sortedPositions.forEach((pos) => {
      orderMap.set(pos.point, pos.order);
    });

    // 计算哪些探测点应该显示名称
    const shouldShowNames = this.shouldShowDetectionName(pointPositions);

    // 绘制探测点
    pointPositions.forEach(({ point, screenX, screenY }, index) => {
      const isHovered = this.hoveredDetectionPoint === point;
      const isSpecial = point.is_special !== 0;
      const isSelected = this.selectedPoints.has(point);
      
      // 检查files数量
      const filesCount = point.data_set_list ? point.data_set_list.length : 0;
      const hasFiles = filesCount > 0;

      // 设置颜色
      let fillColor = "#00ff00"; // 默认绿色
      if (isHovered) {
        fillColor = "#ffff00"; // 悬停黄色
      } else if (isSelected) {
        fillColor = isSpecial ? "#ff6600" : "#0066ff"; // 选中时：特殊点橙色，普通点蓝色
      } else if (isSpecial) {
        fillColor = "#ff0000"; // 特殊点红色
      } else if (!hasFiles) {
        fillColor = "#808080"; // files数量为空时显示灰色
      }

      ctx.fillStyle = fillColor;
      ctx.strokeStyle = isSelected ? "#ffff00" : "#ffffff"; // 选中时用黄色边框
      ctx.lineWidth = isSelected ? 3 / this.scale : 2 / this.scale;

      if (isSpecial) {
        // 绘制红色五角星
        this.drawStar(ctx, screenX, screenY, (isHovered ? 10 : 8) / this.scale);
      } else {
        // 绘制圆形
        ctx.beginPath();
        ctx.arc(screenX, screenY, (isHovered ? 8 : 6) / this.scale, 0, 2 * Math.PI);
        ctx.fill();
        ctx.stroke();
      }

      // 绘制探测点名称 - 使用智能显示逻辑
      if (point.detection_num && shouldShowNames[index]) {
        ctx.fillStyle = "#ffffff";
        ctx.font = `${12 / this.scale}px Arial`;
        ctx.textAlign = "center";
        ctx.textBaseline = "top";
        // 添加文字描边，提高可读性
        ctx.strokeStyle = "#000000";
        ctx.lineWidth = 3 / this.scale;
        ctx.strokeText(point.detection_num, screenX, screenY + 14 / this.scale);
        ctx.fillText(point.detection_num, screenX, screenY + 14 / this.scale);
      }
    });

    ctx.restore();

    // 绘制选区（在屏幕坐标系中绘制）
    if (this.selectionPoints && this.selectionClickCount === 1) {
      const [start, end] = this.selectionPoints;

      // 将经纬度坐标转换为屏幕坐标
      const mapSize = this.getMapSize(level);
      const latRange = this.latLngBounds.maxLat - this.latLngBounds.minLat;
      const lngRange = this.latLngBounds.maxLng - this.latLngBounds.minLng;

      const startScreenX = ((start.lng - this.latLngBounds.minLng) / lngRange) * mapSize;
      const startScreenY = ((this.latLngBounds.maxLat - start.lat) / latRange) * mapSize;
      const endScreenX = ((end.lng - this.latLngBounds.minLng) / lngRange) * mapSize;
      const endScreenY = ((this.latLngBounds.maxLat - end.lat) / latRange) * mapSize;

      // 转换为屏幕坐标
      const x1 = startScreenX * this.scale + this.panX;
      const y1 = startScreenY * this.scale + this.panY;
      const x2 = endScreenX * this.scale + this.panX;
      const y2 = endScreenY * this.scale + this.panY;

      const x = Math.min(x1, x2);
      const y = Math.min(y1, y2);
      const width = Math.abs(x2 - x1);
      const height = Math.abs(y2 - y1);

      // 如果选区太小，显示一个最小尺寸的选区框
      const minSize = 10;
      const finalWidth = Math.max(width, minSize);
      const finalHeight = Math.max(height, minSize);

      // 保存当前状态
      ctx.save();

      // 绘制选区框
      ctx.fillStyle = "rgba(0, 150, 255, 0.1)";
      ctx.fillRect(x, y, finalWidth, finalHeight);

      ctx.strokeStyle = "rgba(0, 150, 255, 0.8)";
      ctx.lineWidth = 2;
      ctx.setLineDash([5, 5]);
      ctx.strokeRect(x, y, finalWidth, finalHeight);
      ctx.setLineDash([]);

      // 恢复状态
      ctx.restore();
    }
  }

  // 启动渲染循环
  private startRenderLoop(): void {
    const loop = () => {
      this.loadVisibleTiles();
      this.render();
      this.animationFrameId = requestAnimationFrame(loop);
    };
    loop();
  }

  // 停止渲染循环
  private stopRenderLoop(): void {
    if (this.animationFrameId !== null) {
      cancelAnimationFrame(this.animationFrameId);
      this.animationFrameId = null;
    }
  }

  // 屏幕坐标转经纬度
  private screenToLatLng(screenX: number, screenY: number): { lat: number; lng: number } | null {
    const mapX = (screenX - this.panX) / this.scale;
    const mapY = (screenY - this.panY) / this.scale;

    const mapSize = this.getMapSize();

    // 归一化到 0-1 范围
    const normalizedX = mapX / mapSize;
    const normalizedY = mapY / mapSize;

    // 使用实际的经纬度范围计算
    const lngRange = this.latLngBounds.maxLng - this.latLngBounds.minLng;
    const latRange = this.latLngBounds.maxLat - this.latLngBounds.minLat;

    const lng = this.latLngBounds.minLng + normalizedX * lngRange;
    const lat = this.latLngBounds.maxLat - normalizedY * latRange; // Y轴反转

    return { lat, lng };
  }

  // 检查是否悬停在探测点上
  private checkDetectionPointHover(screenX: number, screenY: number): PatrolDetectionPoint | null {
    const mapSize = this.getMapSize();

    // 使用实际的经纬度范围进行坐标转换
    const latRange = this.latLngBounds.maxLat - this.latLngBounds.minLat;
    const lngRange = this.latLngBounds.maxLng - this.latLngBounds.minLng;

    for (const point of this.detectionPoints) {
      // 归一化到 0-1 范围
      const normalizedLng = (point.center_longitude - this.latLngBounds.minLng) / lngRange;
      const normalizedLat = (point.center_latitude - this.latLngBounds.minLat) / latRange;

      // 转换为地图坐标
      const pointScreenX = normalizedLng * mapSize;
      const pointScreenY = (1 - normalizedLat) * mapSize;

      const screenPointX = pointScreenX * this.scale + this.panX;
      const screenPointY = pointScreenY * this.scale + this.panY;

      const distance = Math.sqrt(Math.pow(screenX - screenPointX, 2) + Math.pow(screenY - screenPointY, 2));

      if (distance <= 8) {
        return point;
      }
    }

    return null;
  }

  // 事件处理器
  private handleWheel(e: WheelEvent): void {
    e.preventDefault();

    // 如果正在拖拽，先停止拖拽
    if (this.isDragging) {
      this.isDragging = false;
      this.dragOffset = null;
    }

    const rect = this.canvas.getBoundingClientRect();
    const mouseX = e.clientX - rect.left;
    const mouseY = e.clientY - rect.top;

    // 计算新的逻辑缩放比例
    const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
    const newLogicalScale = Math.min(10, Math.max(0.5, this.logicalScale * zoomFactor));

    // 如果缩放比例没有改变（达到了边界），则不进行任何操作
    if (newLogicalScale === this.logicalScale) {
      return;
    }

    // 计算鼠标在归一化坐标系中的位置（0-1范围）
    // 注意：这里使用基准大小1024作为归一化基准
    const baseSize = 1024;
    const oldWorldX = (mouseX - this.panX) / this.logicalScale;
    const oldWorldY = (mouseY - this.panY) / this.logicalScale;

    // 归一化坐标（相对于基准大小）
    const normalizedX = oldWorldX / baseSize;
    const normalizedY = oldWorldY / baseSize;

    // 更新逻辑缩放和层级
    this.logicalScale = newLogicalScale;
    this.currentLevel = this.calculateLevel(newLogicalScale);

    // 在新逻辑缩放下的世界坐标
    const newWorldX = normalizedX * baseSize;
    const newWorldY = normalizedY * baseSize;

    // 调整平移量，使得鼠标位置在地图上的点保持不变
    this.panX = mouseX - newWorldX * newLogicalScale;
    this.panY = mouseY - newWorldY * newLogicalScale;
  }

  /**
   * 判断是否处于选区模式
   * 当按下Meta键（macOS下为⌘，Windows下为Ctrl）时为选区模式
   * @returns {boolean}
   */
  private isSelectionMode(): boolean {
    return this.simulatedMetaKey === true;
  }

  private handleMouseDown(e: MouseEvent): void {
    if (this.isSelectionMode()) {
      return;
    }

    const rect = this.canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    this.isDragging = true;
    this.dragOffset = { x: x - this.panX, y: y - this.panY };
    this.canvas.style.cursor = "grabbing";
  }

  private handleMouseMove(e: MouseEvent): void {
    const rect = this.canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    // 处理拖拽
    if (this.isDragging && this.dragOffset) {
      this.panX = x - this.dragOffset.x;
      this.panY = y - this.dragOffset.y;
      return;
    }

    if (this.isSelectionMode()) {
      // 处理选区模式下 mousemove 时的逻辑：动态设置选区第二个点，实现实时绘制选区矩形
      if (this.selectionPoints && this.selectionClickCount === 1) {
        const coord = this.screenToLatLng(x, y);
        if (coord) {
          // 实时更新第二个点
          this.selectionPoints = [this.selectionPoints[0], coord];
        }
      }
      return;
    }

    // 光标样式由JSX中的style控制，这里不需要设置

    // 处理悬停
    const hoveredPoint = this.checkDetectionPointHover(x, y);
    if (hoveredPoint !== this.hoveredDetectionPoint) {
      this.hoveredDetectionPoint = hoveredPoint;
      this.onDetectionPointHover?.(hoveredPoint);
    }

    // 坐标悬停回调
    const coord = this.screenToLatLng(x, y);
    this.onCoordinateHover?.(coord);
  }

  private handleMouseUp(): void {
    if (this.isSelectionMode()) {
      return;
    }
    this.isDragging = false;
    this.dragOffset = null;
    // 光标样式由JSX中的style控制
  }

  private handleClick(e: MouseEvent): void {
    if (this.isDragging) return;

    const rect = this.canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    // 选区模式：优先使用模拟Meta键，如果没有则使用真实Meta键
    const selectionMode = this.isSelectionMode();
    if (selectionMode) {
      const coord = this.screenToLatLng(x, y);
      if (coord) {
        if (this.selectionClickCount === 0) {
          // 第一次点击
          this.selectionPoints = [coord, coord];
          this.selectionClickCount = 1;
        } else if (this.selectionClickCount === 1 && this.selectionPoints) {
          // 第二次点击
          this.selectionPoints = [this.selectionPoints[0], coord];
          this.selectionClickCount = 0;

          // 完成选区，计算选区矩形
          const [start, end] = this.selectionPoints;
          const lngRange = getCorrectLngRange(start.lng, end.lng);
          const selectionRect: LatLngRect = {
            minLat: Math.min(start.lat, end.lat),
            maxLat: Math.max(start.lat, end.lat),
            minLng: lngRange.minLng,
            maxLng: lngRange.maxLng,
          };

          // 查找与选区相交的探测点
          const intersectingDetectionPoints = this.detectionPoints.filter((point) => {
            return pointIntersectsRect({ lat: point.center_latitude, lng: point.center_longitude }, selectionRect);
          });

          // 更新选中的探测点
          this.selectedPoints.clear();
          intersectingDetectionPoints.forEach((point) => this.selectedPoints.add(point));

          // 选区结束回调
          if (this.onSelectionEnd) {
            this.onSelectionEnd(selectionRect, intersectingDetectionPoints);
          }

          // 重置选区状态
          this.selectionPoints = null;
          this.selectionClickCount = 0;
        }
      }
      return;
    }

    // 检查是否点击了探测点
    const clickedPoint = this.checkDetectionPointHover(x, y);
    if (clickedPoint) {
      this.onDetectionPointClick?.(clickedPoint);
      return;
    }

    // 坐标点击回调
    const coord = this.screenToLatLng(x, y);
    this.onCoordinateClick?.(coord);
  }

  // 公共方法
  public setData(detectionPoints: PatrolDetectionPoint[]): void {
    this.detectionPoints = detectionPoints;
    // 清除名称显示缓存，因为数据已改变
    this.nameVisibilityCache = null;
  }

  public zoomIn(): void {
    const newLogicalScale = Math.min(10, this.logicalScale * 1.2);
    const scaleRatio = newLogicalScale / this.logicalScale;
    const canvasCenterX = this.canvas.width / 2;
    const canvasCenterY = this.canvas.height / 2;

    this.panX = canvasCenterX - (canvasCenterX - this.panX) * scaleRatio;
    this.panY = canvasCenterY - (canvasCenterY - this.panY) * scaleRatio;
    this.logicalScale = newLogicalScale;
    this.currentLevel = this.calculateLevel(newLogicalScale);
  }

  public zoomOut(): void {
    const newLogicalScale = Math.max(0.5, this.logicalScale * 0.8);
    const scaleRatio = newLogicalScale / this.logicalScale;
    const canvasCenterX = this.canvas.width / 2;
    const canvasCenterY = this.canvas.height / 2;

    this.panX = canvasCenterX - (canvasCenterX - this.panX) * scaleRatio;
    this.panY = canvasCenterY - (canvasCenterY - this.panY) * scaleRatio;
    this.logicalScale = newLogicalScale;
    this.currentLevel = this.calculateLevel(newLogicalScale);
  }

  public resetView(): void {
    this.fitToCanvas();
  }

  public updateTileUrlTemplate(tileUrlTemplate: string): void {
    if (this.tileUrlTemplate !== tileUrlTemplate) {
      this.tileUrlTemplate = tileUrlTemplate;
      this.tileCache.clear();
      this.tileLoadingSet.clear();
      this.failedTileSet.clear();
      this.resetView();
    }
  }

  public updateLatLngBounds(latLngBounds: LatLngBounds): void {
    this.latLngBounds = latLngBounds;
  }

  public getSelectedPoints(): PatrolDetectionPoint[] {
    return Array.from(this.selectedPoints);
  }

  public clearSelection(): void {
    this.selectedPoints.clear();
    this.selectionPoints = null;
    this.selectionClickCount = 0;
  }

  public updateSimulatedMetaKey(simulatedMetaKey: boolean): void {
    this.simulatedMetaKey = simulatedMetaKey;
  }

  public destroy(): void {
    this.stopRenderLoop();
    this.removeEventListeners();
    this.tileCache.clear();
    this.tileLoadingSet.clear();
    this.failedTileSet.clear();
  }
}
